﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ESP
{
    public abstract class Network : ICloneable
    {
        private int inputCount;
        private int outputCount;
        protected double[] activation;
        public double fitness;
        private const double LESION_THRESHOLD = 0.9;
        private Neuron[] neurons; // Neuron
//	private int geneCount;

        //Functions added by Jacob Schrum, August 2007
        public double[] getActivation()
        {
            return activation;
        }

        public Neuron[] getNeurons()
        {
            return neurons;
        }

        //End functions by Jacob Schrum

        private static double sigmoid(double x, double slope)
        {
            return (1/(1 + Math.Exp(-(slope*x))));
        }

        protected static double sigmoid(double x)
        {
            return sigmoid(x, 1.0);
        }

        // , getGeneSize()
        public Network(int inputCount, int neuronCount, int outputCount)
        {
            this.inputCount = inputCount;
            this.outputCount = outputCount;
            activation = new double[neuronCount];
            if (EspAlgorithm.MIN)
            {
                fitness = 10000000;
            }
            else
            {
                fitness = 0.0;
            }
            neurons = new Neuron[neuronCount];
            for (int i = 0; i < neurons.Length; i++)
            {
                neurons[i] = new Neuron(getGeneSize());
            }
        }

        public virtual Object Clone()
        {
            try
            {
                Network rc = (Network) this.MemberwiseClone();
                rc.activation = (double[]) rc.activation.Clone();
                rc.neurons = (Neuron[]) rc.neurons.Clone();
                for (int i = 0; i < rc.neurons.Length; i++)
                {
                    rc.neurons[i] = (Neuron) rc.neurons[i].Clone();
                }
                return rc;
            }
            catch (NotSupportedException e)
            {
                return null;
            }
        }

        public void copy(Network net)
        {

        }

/*	  
	public Network( Network n ) {
		inputCount = n.inputCount;
		outputCount = n.outputCount;
		activation = new double[n.activation.Length];
		for( int i = 0; i < activation.Length; i++ ) {
			activation[i] = n.activation[i];
		}
		fitness = n.fitness;
		neurons = new Neuron[n.getNeuronCount()];
		for( int i = 0; i < n.getNeuronCount(); ++i ) {
    		setNeuron( i, n.getNeuron(i) );
    	}
	}
*/
        //  Network(const Network &n) : subPop(n){;}
        public void resetActivation()
        {
            //Arrays.fill( activation, 0.0 );
            for (int i = 0; i < activation.Length; i++)
            {
                activation[i] = 0.0;
            }
        }

/*	  
	public void setNeuron( Neuron n, int position ) {
		setNeuron(position, n);
	}
	  
	public void setNetwork( Network n ) {
		fitness = n.fitness;
		for(int i = 0; i < getNeuronCount(); ++i) {
    		setNeuron(i, n.getNeuron(i));
    	}
	}
*/

        public void addFitness()
        {
            for (int i = 0; i < getNeuronCount(); ++i)
            {
                getNeuron(i).fitness += fitness;
            }
        }

        public void incrementTests()
        {
            for (int i = 0; i < getNeuronCount(); ++i)
            {
                ++getNeuron(i).tests;
            }
        }

        public int lesion(IEnvironment e)
        {
            int sp = -1;
            double lesionFitness, max = 0.0;
            fitness = e.evalNet(this);
            Console.WriteLine("UNlesioned : " + fitness);
            for (int i = 0; i < getNeuronCount(); ++i)
            {
                getNeuron(i).lesioned = true;
                lesionFitness = e.evalNet(this);
                Console.WriteLine("lesion " + i + " : " + lesionFitness);
                getNeuron(i).lesioned = false;
                if (lesionFitness > max)
                {
                    max = lesionFitness;
                    sp = i;
                }
            }
            if (max < (fitness*Network.LESION_THRESHOLD))
            {
                sp = -1;
            }
            return sp;
        }

        public int getInputCount()
        {
            return inputCount;
        }

        public int getOutputCount()
        {
            return outputCount;
        }

        public int getPopulationSize()
        {
            return neurons.Length;
        }

//	public int getGeneCount() {
//		return geneCount;
//	}

        public virtual void addNeuron()
        {
            Neuron[] temp = new Neuron[neurons.Length + 1];
            Array.Copy(neurons, 0, temp, 0, neurons.Length);
            temp[neurons.Length] = new Neuron(getGeneSize());
            neurons = temp;
        }

        public virtual void removeNeuron(int index)
        {
            Neuron[] temp = new Neuron[neurons.Length - 1];
            Array.Copy(neurons, 0, temp, 0, index);
            Array.Copy(neurons, index + 1, temp, index, neurons.Length - index - 1);
            neurons = temp;
        }

/*	
	private void setNeuron( int index, Neuron n ) {
		neurons[index] = (Neuron)n.clone();
	}
*/

        public Neuron getNeuron(int index)
        {
            return neurons[index];
        }

        public int getNeuronCount()
        {
            return neurons.Length;
        }

        public void create()
        {
            // creates a random subpopulation of neurons
            for (int i = 0; i < neurons.Length; ++i)
            {
                neurons[i] = new Neuron(getGeneSize());
                neurons[i].create();
            }
        }

        public int load(String filename)
        {

            //BufferedReader br = new BufferedReader( new InputStreamReader( new FileInputStream( filename ) ) ) ;
            //StreamTokenizer st = new StreamTokenizer( br );
            //st.nextToken();
            //neurons = new Neuron[(Integer.valueOf(st.toString())).intValue()];
            //int geneCount = (Integer.valueOf(st.toString())).intValue();
            //for( int i = 0; i < neurons.Length; ++i ) {
            //    neurons[i] = new Neuron(geneCount);
            //    for( int j = 0; j < geneCount; ++j ) {
            //        st.nextToken();
            //        neurons[i].weight[j] = Double.valueOf(st.toString()).doubleValue();
            //    }
            //}
            //br.close();

            //return neurons.Length;

            throw new NotImplementedException();
        }

        public void setRandomNetwork(SubPopulation[] pops)
        {
            for (int j = 0; j < pops.Length; ++j)
            {
                neurons[j] = pops[j].selectNeuron();
//			setNeuron( j, pops[j].selectNeuron() );
            }
        }

        public void saveText(String fname)
        {
            //try {
            //    PrintWriter pw = new PrintWriter( new FileOutputStream( fname ) );
            //    pw.println( neurons.Length );
            //    pw.println( getGeneSize() );
            //    for( int i = 0; i < neurons.Length; ++i ) {
            //        for( int j = 0; j < getGeneSize(); ++j ) {
            //            pw.print( neurons[i].weight[j] );
            //        }
            //        pw.println();
            //    }
            //    pw.close();
            //} catch( IOException e ) {
            //    Console.WriteLine();
            //    System.out.print(" Error - cannot open " + fname);
            //    System.exit(1);
            //}

            throw new NotImplementedException();

        }

        public void addConnection(int locus)
        {
            // TODO: Make more efficient
            for (int i = 0; i < neurons.Length; ++i)
            {
                neurons[i].weight = doubleArrayInsert(neurons[i].weight, 1.0, locus);
            }
        }

//----------------------------------------------------------------------
// opposite of addConnection.
        public void removeConnection(int locus)
        {
            if (locus < neurons.Length)
            {
                for (int i = 0; i < neurons.Length; ++i)
                {
                    neurons[i].weight = doubleArrayRemove(neurons[i].weight, locus);
                }
            }
        }

        public abstract int getGeneSize();
        public abstract int getTotalInputs(); // returns sum of environment and recurrent inputs
        public abstract void activate(double[] input, double[] output);
        public abstract void save(String filename);
        //  public void print();
        private double[] doubleArrayInsert(double[] array, double value, int position)
        {
            double[] rc = new double[array.Length + 1];
            Array.Copy(array, 0, rc, 0, position);
//		for( int i = 0; i < position; i++ ) {
//			rc[i] = array[i];
//		}
            rc[position] = value;
            Array.Copy(array, position, rc, position + 1, array.Length - position);
//		for( int i = position; i < array.Length; i++ ) {
//			rc[i+1] = array[i];
//		}
            return rc;
        }

        private double[] doubleArrayRemove(double[] array, int position)
        {
            double[] rc = new double[array.Length - 1];
            Array.Copy(array, 0, rc, 0, position);
//		for( int i = 0; i < position; i++ ) {
//			rc[i] = array[i];
//		}
            Array.Copy(array, position + 1, rc, position, array.Length - position - 1);
//		for( int i = position+1; i < array.Length; i++ ) {
//			rc[i-1] = array[i];
//		}
            return rc;
        }
    }
}
